package paril.SlabUtilities;

import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;

import sun.security.util.BitArray;

public final class BlockRoutines
{
	static Vector Cross(Vector u, Vector v)
	{
	     return new Vector(
	    		 (u.getY() * v.getZ()) - (u.getZ() * v.getY()),
	    		 -((u.getX() * v.getZ()) - (u.getZ() * v.getX())),
	    		 (u.getX() * v.getY()) - (u.getY() * v.getX()));
	}

	static double Dot(Vector a, Vector b)
	{
		double i = (a.getX()*b.getX())+(a.getY()*b.getY())+(a.getZ()*b.getZ());
	     return i;
	}
	
	static int IntersectTri(Vector p, Vector q, Vector a, Vector b, Vector c, double[] out)
	{
		Vector
				qp = new Vector(),
				ab = new Vector(),
				ac = new Vector(),
				ap = new Vector(),
				e = new Vector();
	
	     qp.setX(p.getX() - q.getX());
	     qp.setY(p.getY() - q.getY());
	     qp.setZ(p.getZ() - q.getZ());
	
	     ab.setX(b.getX() - a.getX());
	     ab.setY(b.getY() - a.getY());
	     ab.setZ(b.getZ() - a.getZ());
	
	     ac.setX(c.getX() - a.getX());
	     ac.setY(c.getY() - a.getY());
	     ac.setZ(c.getZ() - a.getZ());
	
	     //Compute Normal
	     Vector n = Cross(ab, ac);
	
	     //Compute denominator
	     double d = Dot(qp, n);
	     if (d<= 0.0) return 2;     //Ray is parallel to or points away from triangle
	
	     //Compute Ray Intersection
	     ap.setX(p.getX() - a.getX());
	     ap.setY(p.getY() - a.getY());
	     ap.setZ(p.getZ() - a.getZ());
	
	     out[3] = Dot(ap, n);
	     if (out[3] < 0.0f) return 0;
	     //if (t > d) return 0     //This test for segment only, not ray test
	
	     //Compute Barycentric Coordinates, and test bounds
	     e = Cross(qp, ap);
	     out[1] = Dot(ac, e);
	     if (out[1] < 0.0f || out[1] > d) return 0;
	     out[2] = -Dot(ab, e);
	     if (out[2] < 0.0f || out[1]+out[2] > d) return 0;
	
	     //Intersection Valid, compute last coordinate
	     double ood = 1.0 / d;
	     out[3] *= ood;
	     out[1] *= ood;
	     out[2] *= ood;
	     out[0] = 1.0 - out[1] - out[2];
	     return 1;
	}
	
	static double halfSpace(Vector p, Vector q, Vector a, Vector c)
	{
		Vector pq = new Vector(), pa = new Vector(), pc = new Vector();

	     pq.setX(q.getX() - p.getX());
	     pq.setY(q.getY() - p.getY());
	     pq.setZ(q.getZ() - p.getZ());

	     pa.setX(a.getX() - p.getX());
	     pa.setY(a.getY() - p.getY());
	     pa.setZ(a.getZ() - p.getZ());

	     pc.setX(c.getX() - p.getX());
	     pc.setY(c.getY() - p.getY());
	     pc.setZ(c.getZ() - p.getZ());

	     Vector m = Cross(pc, pq);
	     double v = Dot(pa, m);

	     return v;
	}
	
	public static float TestSide (BlockFace face, Block block, Player player)
	{
		Vector a, b, c, d;
		
		switch (face)
		{
		case WEST:
			a = block.getLocation().toVector();
			b = block.getLocation().add(0, 0, 1).toVector();
			c = block.getLocation().add(0, 1, 1).toVector();
			d = block.getLocation().add(0, 1, 0).toVector();
			break;
		case EAST:
			a = block.getLocation().add(1, 0, 0).toVector();
			d = block.getLocation().add(1, 0, 1).toVector();
			c = block.getLocation().add(1, 1, 1).toVector();
			b = block.getLocation().add(1, 1, 0).toVector();
			break;
		case NORTH:
			a = block.getLocation().toVector();
			b = block.getLocation().add(0, 1, 0).toVector();
			c = block.getLocation().add(1, 1, 0).toVector();
			d = block.getLocation().add(1, 0, 0).toVector();
			break;
		case SOUTH:
			a = block.getLocation().add(0, 0, 1).toVector();
			d = block.getLocation().add(0, 1, 1).toVector();
			c = block.getLocation().add(1, 1, 1).toVector();
			b = block.getLocation().add(1, 0, 1).toVector();
			break;
		case DOWN:
			a = block.getLocation().toVector();
			b = block.getLocation().add(1, 0, 0).toVector();
			c = block.getLocation().add(1, 0, 1).toVector();
			d = block.getLocation().add(0, 0, 1).toVector();
			break;
			
		case UP:
			a = block.getLocation().add(0, 1, 0).toVector();
			d = block.getLocation().add(1, 1, 0).toVector();
			c = block.getLocation().add(1, 1, 1).toVector();
			b = block.getLocation().add(0, 1, 1).toVector();
			break;
		default:
			return -1;
		}
		
		Vector p = player.getEyeLocation().toVector(),
			   q = player.getEyeLocation().add(player.getEyeLocation().getDirection().multiply(8192)).toVector();
		
		//Barycentric coordinate results
		double[] out = new double[] { 0, 0, 0, 0 };
		out[3] = halfSpace(p, q, a, c);
		int triTest;
		
		if (out[3] > 0.0)
			triTest = IntersectTri(p, q, a, b, c, out);
		else
			triTest = IntersectTri(p, q, a, c, d, out);
		
		if (triTest != 1)
			return -1;
		
		// Special case for top/bottom
		if (face == BlockFace.UP)
			return 1;
		else if (face == BlockFace.DOWN)
			return 0;
		
		double len = (p.subtract(q)).length();
		Vector f = player.getEyeLocation().add(player.getEyeLocation().getDirection().multiply(len * out[3])).toVector();
		
		switch (face)
		{
		case WEST:
		case NORTH:
		case SOUTH:
		case EAST:
			Vector sub = (f.subtract(a));
			return (float)sub.getY();
		default:
			return -1;
		}
	}
	
	public static boolean AimingAtTopOfBlock(Player player, Block block)
	{
		final BlockFace[] faces = { BlockFace.NORTH, BlockFace.EAST, BlockFace.WEST, BlockFace.SOUTH, BlockFace.UP, BlockFace.DOWN };
		
		for (BlockFace face : faces)
		{
			float test = TestSide(face, block, player);
			
			if (test < 0)
				continue;
			else if (test < 0.5f)
				return false;
			else
				return true;
		}
		
		return false;
	}
	
	public static byte SetStepUpsideDown(byte data, boolean isUpsideDown)
	{
		BitArray bits = new BitArray(8, new byte[] { data });
		bits.set(4, isUpsideDown);
		return bits.toByteArray()[0];
	}

	public static byte GetStepUpsideDown(byte inData, byte blockData)
	{
		BitArray bits = new BitArray(8, new byte[] { inData });
		BitArray inBits = new BitArray(8, new byte[] { blockData });
		bits.set(4, inBits.get(4));
		return bits.toByteArray()[0];
	}
}
